package org.box.metadate.cli;

import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.cli.Options;
import org.box.metadate.cli.annotation.Argument;
import org.box.metadate.cli.annotation.Arguments;

/**
 * A formatter of help messages for the given command line configuration.
 * 
 * @author <a href="mailto:alexander.box@gmail.com">Alexander Korobka</a>
 */
public class HelpFormatter<T> {

	private final org.apache.commons.cli.HelpFormatter delegate = new org.apache.commons.cli.HelpFormatter();
	private final Class<T> cfgClass;

	/**
	 * Constructs a new instance of {@link HelpFormatter}
	 * 
	 * @param cfgClass - the class of application's configuration
	 */
	public HelpFormatter(Class<T> cfgClass) {
		this.cfgClass = cfgClass;
	}

	/**
	 * Generates usage section for help. If non-Enumerated Arguments field is
	 * present in the <code>configuration</code> and it has non-empty syntax
	 * parameter, it is being returned otherwise an aggregated string from
	 * Enumerated Argument fields
	 * 
	 * @return usage section
	 */
	public String generateCmdLineSyntax() {

		String syntax = getNonEnumeratedArgsSyntax();
		if (syntax == null || "".equals(syntax.trim())) {
			return getEnumeratedArgsSyntax();
		}

		return syntax;
	};

	/**
	 * Generates a syntax string from Enumerated Arguments for help section
	 * 
	 * @return a usage syntax as string
	 */
	protected String getEnumeratedArgsSyntax() {
		List<Argument> args = new ArrayList<Argument>();
		for (Field f : cfgClass.getDeclaredFields()) {
			// check enumerated arguments
			if (f.isAnnotationPresent(Argument.class)) {
				args.add(f.getAnnotation(Argument.class));
			}
		}
		Collections.sort(args, new Comparator<Argument>() {

			public int compare(Argument o1, Argument o2) {
				return new Integer(o1.index()).compareTo(o2.index());
			}
		});

		StringBuilder sb = new StringBuilder();
		for (Argument a : args) {
			sb.append(a.required() ? a.name() + " " : "[" + a.name() + "] ");
		}

		return sb.toString();
	}

	/**
	 * Generates a syntax string from non-Enumerated Arguments for help section
	 * 
	 * @return a usage syntax as string
	 */
	protected String getNonEnumeratedArgsSyntax() {
		for (Field f : cfgClass.getDeclaredFields()) {
			// check enumerated arguments
			if (f.isAnnotationPresent(Arguments.class)) {
				Arguments as = f.getAnnotation(Arguments.class);
				return as.syntax();
			}
		}
		return null;
	}

	/**
	 * Print the help for <code>configuration</code> with auto generated command
	 * line syntax. This method prints help information to System.out.
	 * 
	 * @param cmdLineSyntax
	 *            the syntax for this application
	 * @throws ValidationException
	 *             if the given class is not compatible or valid as an
	 *             Application's configuration 

	 */
	public void printHelp() throws ValidationException {
		printHelp(org.apache.commons.cli.HelpFormatter.DEFAULT_WIDTH,
				getEnumeratedArgsSyntax(), null, null, false);
	}

	/**
	 * Print the help for <code>configuration</code> with the specified command
	 * line syntax. This method prints help information to System.out.
	 * 
	 * @param cmdLineSyntax
	 *            the syntax for this application
	 * @throws ValidationException
	 *             if the given class is not compatible or valid as an
	 *             Application's configuration 

	 */
	public void printHelp(String cmdLineSyntax) throws ValidationException {
		printHelp(org.apache.commons.cli.HelpFormatter.DEFAULT_WIDTH,
				cmdLineSyntax, null, null, false);
	}

	/**
	 * Print the help for <code>configuration</code> with the specified command
	 * line syntax. This method prints help information to System.out.
	 * 
	 * @param cmdLineSyntax
	 *            the syntax for this application
	 * @param autoUsage
	 *            whether to print an automatically generated usage statement
	 * @throws ValidationException
	 *             if the given class is not compatible or valid as an
	 *             Application's configuration 
	 */
	public void printHelp(String cmdLineSyntax, boolean autoUsage)
			throws ValidationException {
		printHelp(org.apache.commons.cli.HelpFormatter.DEFAULT_WIDTH,
				cmdLineSyntax, null, null, autoUsage);
	}

	/**
	 * Print the help for <code>configuration</code> with the specified command
	 * line syntax. This method prints help information to System.out.
	 * 
	 * @param cmdLineSyntax
	 *            the syntax for this application
	 * @param header
	 *            the banner to display at the begining of the help
	 * @param footer
	 *            the banner to display at the end of the help
	 * @throws ValidationException
	 *             if the given class is not compatible or valid as an
	 *             Application's configuration 

	 */
	public void printHelp(String cmdLineSyntax, String header, String footer)
			throws ValidationException
			{
		printHelp(cmdLineSyntax, header, footer, false);
	}

	/**
	 * Print the help for <code>configuration</code> with the specified command
	 * line syntax. This method prints help information to System.out.
	 * 
	 * @param cmdLineSyntax
	 *            the syntax for this application
	 * @param header
	 *            the banner to display at the begining of the help
	 * @param footer
	 *            the banner to display at the end of the help
	 * @param autoUsage
	 *            whether to print an automatically generated usage statement
	 * @throws ValidationException
	 *             if the given class is not compatible or valid as an
	 *             Application's configuration 
	 */
	public void printHelp(String cmdLineSyntax, String header, String footer,
			boolean autoUsage) throws ValidationException  {
		printHelp(org.apache.commons.cli.HelpFormatter.DEFAULT_WIDTH,
				cmdLineSyntax, header, footer, autoUsage);
	}

	/**
	 * Print the help for <code>configuration</code> with the specified command
	 * line syntax. This method prints help information to System.out.
	 * 
	 * @param width
	 *            the number of characters to be displayed on each line
	 * @param cmdLineSyntax
	 *            the syntax for this application
	 * @param header
	 *            the banner to display at the beginning of the help
	 * @param footer
	 *            the banner to display at the end of the help
	 * @throws ValidationException
	 *             if the given class is not compatible or valid as an
	 *             Application's configuration 
	 */
	public void printHelp(int width, String cmdLineSyntax, String header,
			String footer) throws ValidationException{
		printHelp(width, cmdLineSyntax, header, footer, false);
	}

	/**
	 * Print the help for <code>configuration</code> with the specified command
	 * line syntax. This method prints help information to System.out.
	 * 
	 * @param width
	 *            the number of characters to be displayed on each line
	 * @param cmdLineSyntax
	 *            the syntax for this application
	 * @param header
	 *            the banner to display at the begining of the help
	 * @param options
	 *            the Options instance
	 * @param footer
	 *            the banner to display at the end of the help
	 * @param autoUsage
	 *            whether to print an automatically generated usage statement
	 * @throws ValidationException
	 *             if the given class is not compatible or valid as an
	 *             Application's configuration 
	 */
	public void printHelp(int width, String cmdLineSyntax, String header,
			String footer, boolean autoUsage) throws ValidationException {
		PrintWriter pw = new PrintWriter(System.out);

		printHelp(pw, width, cmdLineSyntax, header,
				org.apache.commons.cli.HelpFormatter.DEFAULT_LEFT_PAD,
				org.apache.commons.cli.HelpFormatter.DEFAULT_DESC_PAD, footer,
				autoUsage);
		pw.flush();
	}

	/**
	 * Print the help for <code>configuration</code> with the specified command
	 * line syntax.
	 * 
	 * @param pw
	 *            the writer to which the help will be written
	 * @param width
	 *            the number of characters to be displayed on each line
	 * @param cmdLineSyntax
	 *            the syntax for this application
	 * @param header
	 *            the banner to display at the begining of the help
	 * @param leftPad
	 *            the number of characters of padding to be prefixed to each
	 *            line
	 * @param descPad
	 *            the number of characters of padding to be prefixed to each
	 *            description line
	 * @param footer
	 *            the banner to display at the end of the help
	 * @throws ValidationException
	 *             if the given class is not compatible or valid as an
	 *             Application's configuration 
	 * 
	 * @throws IllegalStateException
	 *             if there is no room to print a line
	 */
	public void printHelp(PrintWriter pw, int width, String cmdLineSyntax,
			String header, int leftPad, int descPad, String footer) throws ValidationException
			 {
		printHelp(pw, width, cmdLineSyntax, header, leftPad, descPad, footer,
				false);
	}

	/**
	 * Print the help for <code>configuration</code> with the specified command
	 * line syntax.
	 * 
	 * @param pw
	 *            the writer to which the help will be written
	 * @param width
	 *            the number of characters to be displayed on each line
	 * @param cmdLineSyntax
	 *            the syntax for this application
	 * @param header
	 *            the banner to display at the begining of the help
	 * @param leftPad
	 *            the number of characters of padding to be prefixed to each
	 *            line
	 * @param descPad
	 *            the number of characters of padding to be prefixed to each
	 *            description line
	 * @param footer
	 *            the banner to display at the end of the help
	 * @param autoUsage
	 *            whether to print an automatically generated usage statement
	 * @throws ValidationException
	 *             if the given class is not compatible or valid as an
	 *             Application's configuration 
	 * @throws IllegalStateException
	 *             if there is no room to print a line
	 */
	public void printHelp(PrintWriter pw, int width, String cmdLineSyntax,
			String header, int leftPad, int descPad, String footer,
			boolean autoUsage) throws ValidationException {
		Options options = MetaCommandLineParser.createParserOptionsInternal(cfgClass);

		delegate.printHelp(pw, width, cmdLineSyntax, header, options, leftPad,
				descPad, footer, autoUsage);
	}

}
